#nullable enable

namespace NServiceBus;

using System;
using System.Threading.Tasks;

/// <summary>
/// Syntactic sugar for <see cref="IPipelineContext" />.
/// </summary>
public static class PipelineContextExtensions
{
    /// <summary>
    /// Sends the provided message.
    /// </summary>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="message">The message to send.</param>
    public static Task Send(this IPipelineContext context, object message)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(message);

        return context.Send(message, new SendOptions());
    }

    /// <summary>
    /// Instantiates a message of <typeparamref name="T" /> and sends it.
    /// </summary>
    /// <typeparam name="T">The type of message, usually an interface.</typeparam>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="messageConstructor">An action which initializes properties of the message.</param>
    /// <remarks>
    /// The message will be sent to the destination configured for <typeparamref name="T" />.
    /// </remarks>
    public static Task Send<T>(this IPipelineContext context, Action<T> messageConstructor)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(messageConstructor);

        return context.Send(messageConstructor, new SendOptions());
    }

    /// <summary>
    /// Sends the message.
    /// </summary>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="destination">The address of the destination to which the message will be sent.</param>
    /// <param name="message">The message to send.</param>
    public static Task Send(this IPipelineContext context, string destination, object message)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentException.ThrowIfNullOrWhiteSpace(destination);
        ArgumentNullException.ThrowIfNull(message);

        var options = new SendOptions();

        options.SetDestination(destination);

        return context.Send(message, options);
    }

    /// <summary>
    /// Instantiates a message of type T and sends it to the given destination.
    /// </summary>
    /// <typeparam name="T">The type of message, usually an interface.</typeparam>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="destination">The destination to which the message will be sent.</param>
    /// <param name="messageConstructor">An action which initializes properties of the message.</param>
    public static Task Send<T>(this IPipelineContext context, string destination, Action<T> messageConstructor)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentException.ThrowIfNullOrWhiteSpace(destination);
        ArgumentNullException.ThrowIfNull(messageConstructor);

        var options = new SendOptions();

        options.SetDestination(destination);

        return context.Send(messageConstructor, options);
    }

    /// <summary>
    /// Sends the message back to the current endpoint.
    /// </summary>
    /// <param name="context">Object being extended.</param>
    /// <param name="message">The message to send.</param>
    public static Task SendLocal(this IPipelineContext context, object message)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(message);

        var options = new SendOptions();

        options.RouteToThisEndpoint();

        return context.Send(message, options);
    }

    /// <summary>
    /// Instantiates a message of type T and sends it back to the current endpoint.
    /// </summary>
    /// <typeparam name="T">The type of message, usually an interface.</typeparam>
    /// <param name="context">Object being extended.</param>
    /// <param name="messageConstructor">An action which initializes properties of the message.</param>
    public static Task SendLocal<T>(this IPipelineContext context, Action<T> messageConstructor)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(messageConstructor);

        var options = new SendOptions();

        options.RouteToThisEndpoint();

        return context.Send(messageConstructor, options);
    }

    /// <summary>
    /// Publish the message to subscribers.
    /// </summary>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="message">The message to publish.</param>
    public static Task Publish(this IPipelineContext context, object message)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(message);

        return context.Publish(message, new PublishOptions());
    }

    /// <summary>
    /// Publish the message to subscribers.
    /// </summary>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <typeparam name="T">The message type.</typeparam>
    public static Task Publish<T>(this IPipelineContext context)
    {
        ArgumentNullException.ThrowIfNull(context);

        return context.Publish<T>(_ => { }, new PublishOptions());
    }

    /// <summary>
    /// Instantiates a message of type T and publishes it.
    /// </summary>
    /// <typeparam name="T">The type of message, usually an interface.</typeparam>
    /// <param name="context">The instance of <see cref="IPipelineContext" /> to use for the action.</param>
    /// <param name="messageConstructor">An action which initializes properties of the message.</param>
    public static Task Publish<T>(this IPipelineContext context, Action<T> messageConstructor)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(messageConstructor);

        return context.Publish(messageConstructor, new PublishOptions());
    }
}